/**
 * @author Rudy Kaplan Depena (The University of Texas at Austin)
 */
package com.utaustin.mirankerlab.ultrawrap.compiler.components;

import java.util.List;
import java.util.ArrayList;

import com.utaustin.mirankerlab.ultrawrap.compiler.main.SparqlToSql;
import com.utaustin.mirankerlab.ultrawrap.compiler.components.RelationalExpr;
import com.utaustin.mirankerlab.ultrawrap.compiler.utilities.Utilities;

/** Triple consists of a subject, predicate and object */
public class Triple {
	
	//static variables
	private static int tripleUniqueId = 1;
	public static List<Triple> triples = new ArrayList<Triple>();
	
	//instance variables
	private Component subjectOrSubjectKey;
	private Component predicate;
	private Component objectOrObjectKey;
	private int tripleId;
	private FilterConstraint filteredConstraint;
	
	/**
	 * Default Constructor
	 */
	public Triple(){
		subjectOrSubjectKey = null;
		predicate = null;
		objectOrObjectKey = null;
		filteredConstraint = null;
		tripleId = tripleUniqueId++;
	}
	
	/**
	 * <p>
	 * Acquire the subject of the triple.
	 * 
	 * <p>
	 * Because this compiler assumes the following relational schema
	 * (subject, subject key, predicate, object, object key)
	 * 
	 * @return Component subject or subject key
	 */
	public Component getSubjectOrSubjectKey() {
		return subjectOrSubjectKey;
	}
	
	/**
	 * <p>
	 * Set the subject of the triple
	 * 
	 * <p>
	 * Because this compiler assumes the following relational schema
	 * (subject, subject key, predicate, object, object key)
	 * 
	 * @param Component subjectOrSubjectKey 
	 */
	public void setSubjectOrSubjectKey(Component subjectOrSubjectKey) {
		this.subjectOrSubjectKey = subjectOrSubjectKey;
	}
	
	/**
	 * <p>
	 * Acquire predicate of triple
	 * 
	 * <p>
	 * Because this compiler assumes the following relational schema
	 * (subject, subject key, predicate, object, object key)
	 * 
	 * @return Component predicate
	 */
	public Component getPredicate() {
		return predicate;
	}
	
	/**
	 * <p>
	 * Set predicate of triple
	 * 
	 * <p>
	 * Because this compiler assumes the following relational schema
	 * (subject, subject key, predicate, object, object key)
	 * 
	 * @param Component predicate
	 */
	public void setPredicate(Component predicate) {
		this.predicate = predicate;
	}
	
	/**
	 * <p>
	 * Acquire an object or object key
	 * 
	 * <p>
	 * Because this compiler assumes the following relational schema
	 * (subject, subject key, predicate, object, object key)
	 * 
	 * @return Component objectOrObjectKey
	 */
	public Component getObjectOrObjectKey() {
		return objectOrObjectKey;
	}
	
	/**
	 * @param objectOrObjectKey the objectOrObjectKey to set
	 */
	public void setObjectOrObjectKey(Component objectOrObjectKey) {
		this.objectOrObjectKey = objectOrObjectKey;
	}
	
	/**
	 * This is an id for the triple
	 * 
	 * @return int tripleId
	 */
	public int getTripleId() {
		return tripleId;
	}
	
	/**
	 * Tests whether or not subject, subject key, predicate, object
	 * or object key is null
	 * @return boolean
	 */
	public boolean subjPredObjOrKeyIsNull(){
		return (subjectOrSubjectKey == null 
			 || predicate == null
			 || objectOrObjectKey == null);
	}
	
	/**
	 * Will return sql statement for Triple within BGP
	 * @param notFirst
	 * @return sql statement
	 */
	public String sqlSelectStatement(boolean notFirst,
			boolean calledByLeftJoin, boolean onlyOneTripleInBGP){
		
		int sNum = SparqlToSql.bookKeeper.getFirstTripleIdEncountered(subjectOrSubjectKey);
		int pNum = SparqlToSql.bookKeeper.getFirstTripleIdEncountered(predicate);
		int oNum = SparqlToSql.bookKeeper.getFirstTripleIdEncountered(objectOrObjectKey);
		
		String sKind = "";
		String pKind = "";
		String oKind = "";
		try
		{
			sKind = SparqlToSql.bookKeeper.getFirstKindOfOccurance(subjectOrSubjectKey, false);
			pKind = SparqlToSql.bookKeeper.getFirstKindOfOccurance(predicate, false);
			oKind = SparqlToSql.bookKeeper.getFirstKindOfOccurance(objectOrObjectKey, false);
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		
		String sqlSelectStatement = "";
		sqlSelectStatement += "(SELECT ";
		//sqlSelectStatement += "SELECT ";
		
		sqlSelectStatement += "spk as spkT" + subjectOrSubjectKey.getTripleId() + ", ";
		sqlSelectStatement += "s as sT" + subjectOrSubjectKey.getTripleId()+ ", ";
		sqlSelectStatement += "p as pT" + predicate.getTripleId()+", ";
		sqlSelectStatement += "opk as opkT" + objectOrObjectKey.getTripleId() + ", ";
		sqlSelectStatement += "o as oT" + objectOrObjectKey.getTripleId();	
		
		///////////////////////////////////////////
		//Juan: I think this is too complicated. Can't we just select * and avoid all these cases?
		// Need to run experiments to see if performance decreases
		//////
		/*
		if(subjectOrSubjectKey.isVar()){
			sqlSelectStatement += "spk as spkT" + subjectOrSubjectKey.getTripleId() + ", ";
			sqlSelectStatement += "s as sT" + subjectOrSubjectKey.getTripleId();
		}
		else if(subjectOrSubjectKey.isSubjectKey())//added to fix issue 5
		{
			sqlSelectStatement += "spk as spkT" + subjectOrSubjectKey.getTripleId();
		}
		
		if((subjectOrSubjectKey.isVar() && predicate.isVar()) || subjectOrSubjectKey.isSubjectKey()){
			sqlSelectStatement += ", p as pT" + predicate.getTripleId();
			System.out.println("here1 = "+predicate.getTripleId() );
			System.out.println("subjectOrSubjectKey.isVar() = "+subjectOrSubjectKey.isVar());
			System.out.println("predicate.isVar() = "+predicate.isVar());
			System.out.println("subjectOrSubjectKey.isSubjectKey() = "+subjectOrSubjectKey.isSubjectKey());
		}
		
		if(!subjectOrSubjectKey.isVar() && predicate.isVar()){
			sqlSelectStatement += "p as pT" + predicate.getTripleId();
			System.out.println("here2 = "+predicate.getTripleId() );
			System.out.println("subjectOrSubjectKey.isVar() = "+subjectOrSubjectKey.isVar());
			System.out.println("predicate.isVar() = "+predicate.isVar());
		}
		
		if(subjectOrSubjectKey.isSubjectKey() || predicate.isVar() || subjectOrSubjectKey.isVar()){
			if(objectOrObjectKey.isVar())
			{
				sqlSelectStatement += ", opk as opkT" + objectOrObjectKey.getTripleId() + ", ";
				sqlSelectStatement += "o as oT" + objectOrObjectKey.getTripleId();	
			}
			else if(objectOrObjectKey.isObjectKey())//added to fix issue 5
			{
				sqlSelectStatement += ", opk as opkT" + objectOrObjectKey.getTripleId() + ", ";
			}
		}
		
		
		if(!subjectOrSubjectKey.isSubjectKey() && !predicate.isVar() && !subjectOrSubjectKey.isVar()){
			if(objectOrObjectKey.isVar())
			{
				sqlSelectStatement += "opk as opkT" + objectOrObjectKey.getTripleId() + ", ";
				sqlSelectStatement += "o as oT" + objectOrObjectKey.getTripleId();	
			}
			else if(objectOrObjectKey.isObjectKey())//added to fix issue 5
			{
				sqlSelectStatement += "opk as opkT" + objectOrObjectKey.getTripleId() + ", ";
			}
		}
		*/
		///////////////////////////
		
		sqlSelectStatement += " FROM tripleview WHERE";
		
		if(!subjectOrSubjectKey.isVar()){
			if(subjectOrSubjectKey.isSubject()){
				sqlSelectStatement += " s = \'" + subjectOrSubjectKey.getSanitizedImg() + "\'";
			}
			//else{
			else if(subjectOrSubjectKey.isSubjectKey())
			{
				sqlSelectStatement += " s = \'" + subjectOrSubjectKey.getSanitizedImg() + "\'";//added to solve issue 4
				//sqlSelectStatement += " spk = " + Utilities.getKey(subjectOrSubjectKey.getSanitizedImg());
				sqlSelectStatement += " AND spk = " + Utilities.getKey(subjectOrSubjectKey.getSanitizedImg());//added to solve issue 4
			}
		}
		if(!predicate.isVar()){
			if(!subjectOrSubjectKey.isVar()){
				sqlSelectStatement += " AND p = \'" + predicate.getSanitizedImg() + "\'"; 
			}
			else{
				sqlSelectStatement += " p = \'" + predicate.getSanitizedImg() + "\'";
			}
		}
		if(!objectOrObjectKey.isVar()){
			if(!subjectOrSubjectKey.isVar() || !predicate.isVar()){
				if(objectOrObjectKey.isObjectKey()){
					sqlSelectStatement += " AND opk = " + Utilities.getKey(objectOrObjectKey.getSanitizedImg());
					sqlSelectStatement += " AND o = \'" + objectOrObjectKey.getSanitizedImg() + "\'";//added to solve issue 4
				}
				else{
					sqlSelectStatement += " AND o = \'" + objectOrObjectKey.getSanitizedImg() + "\'";
				}
			}
			else if(subjectOrSubjectKey.isVar() && predicate.isVar()){
				if(objectOrObjectKey.isObjectKey()){
					sqlSelectStatement += " opk = " + Utilities.getKey(objectOrObjectKey.getSanitizedImg());
					sqlSelectStatement += " AND o = \'" + objectOrObjectKey.getSanitizedImg() + "\'";//added to solve issue 4
				}
				else if(objectOrObjectKey.isObject()){
					sqlSelectStatement += " o = \'" + objectOrObjectKey.getSanitizedImg() + "\'";
				}
			}
		}
		//for filtered constraint
		if(filteredConstraint != null)
		{
			if(filteredConstraint instanceof RelationalExpr)
			{
				String spoForOperand1 = Utilities.givenConstraintGiveSPO(
						subjectOrSubjectKey, predicate, objectOrObjectKey,
						filteredConstraint.getOperand1());
				
				String operator = filteredConstraint.getOperator().getRawImg();
				
				String spoForOperand2 = Utilities.givenConstraintGiveSPO(
						subjectOrSubjectKey, predicate, objectOrObjectKey, 
						filteredConstraint.getOperand2());
				
				if(!subjectOrSubjectKey.isVar() || !predicate.isVar() || !objectOrObjectKey.isVar())
				{
					sqlSelectStatement += " AND ";
				}
				
				if(spoForOperand1 != null && spoForOperand2 != null)
				{
					sqlSelectStatement += spoForOperand1 + " " 
					+ operator + " " + spoForOperand2;
				}
				else if(spoForOperand1 != null)
				{
					sqlSelectStatement += spoForOperand1 + " " 
					+ operator + " " + filteredConstraint.getOperand2().getRawImg();
				}
				else if(spoForOperand2 != null)
				{
					sqlSelectStatement += 
						filteredConstraint.getOperand1().getRawImg() 
						+ " " + operator + " " + spoForOperand2;						
				}
			}
		}
		
		sqlSelectStatement += ") AS t" + tripleId;
				
		if(notFirst || (calledByLeftJoin && onlyOneTripleInBGP ) ){
			sqlSelectStatement += " ON (";
			
			String on = "";
			
			if(subjectOrSubjectKey.getTripleId() > sNum){
				on += "t" + tripleId + "." + "spkT" + tripleId
					+ " = " + "t" + sNum + "." +  sKind + "T" + sNum;
			}
			
			if(objectOrObjectKey.getTripleId() > oNum){
				if((subjectOrSubjectKey.getTripleId() > sNum ||
					predicate.getTripleId() > pNum) && on.equals("") == false){
					on += " AND t" + tripleId + "." + "opkT" + tripleId
					+ " = " + "t" + oNum + "." +  oKind + "T" + oNum;	
				}
				else if((subjectOrSubjectKey.getTripleId() <= sNum &&
						predicate.getTripleId() <= pNum) || on.equals("")){
					on += " t" + tripleId + "." + "opkT" + tripleId
					+ " = " + "t" + oNum + "." +  oKind + "T" + oNum;
				}
			}
			
			sqlSelectStatement += on;
			sqlSelectStatement += ")";
			sqlSelectStatement = sqlSelectStatement.replace("ON ()", " ");
		}
		tripleUniqueId = 1; // Added by juan: Need to restart the Triple Unique Id back to 1
		return sqlSelectStatement;
	}
	
	/**
	 * Set a Filtered Constraint
	 * @param filteredConstraint
	 */
	public void setFilteredConstraint(FilterConstraint filteredConstraint)
	{
		this.filteredConstraint = filteredConstraint;
	}
	
	/**
	 * Returns a String representation of the triple
	 * @return String
	 */
	public String toString(){
		String subj = (subjectOrSubjectKey.isSubject()) ? "s" : "spk";
		String pred = "p";
		String obj = (objectOrObjectKey.isObject()) ? "o" : "opk";
		String result = "";
		result += "< ";
		result += subj + " = " + subjectOrSubjectKey + "; ";
		result += pred + " = " + predicate + "; ";
		result += obj + " = " + objectOrObjectKey + "; ";
		result += tripleId + " >";
		return result;
	}

}
